' Variables used to manage grid Dim IgnoreRowChange As Integer Dim GridInvertRect As RECT Dim GridInverted As Integer Dim GridDropRow As Integer ' Drag mode constants to keep track of dragging activity. Dim DragType As Integer ' type of object being dragged Dim Dragging As Integer ' TRUE when dragging is in progress Dim DragIndex As Integer ' Optional index of dragged obj Dim DragRow As Integer ' Optional row being dragged in grid ' Miscellaneous variables Dim valid% ' used as return for DragValid ' Bitmasks to describe valid drag objects Const MASK_NEWAPPT = 1 ' a new appointment Const MASK_OLDAPPT = 2 ' an old appointment Const MASK_NONE = 0 ' mask used where no drops are allowed Function ApiRectFromPoint (ctl As Grid, X As Single, Y As Single, r As RECT) As Integer ' Given a grid control and a coordinate position, this routine ' returns a Windows RECT structure containing the pixel ' coordinates of the row being pointed at. The row number is ' returned, or -1, indicating that no row is being pointed at. Dim curRow As Integer Dim totHeight As Single Dim topLocation As Single ' Loop through each row, accumulating row height until we reach ' the row containing the point. For curRow = 0 To ctl.Rows - 1 topLocation = totHeight totHeight = totHeight + ctl.RowHeight(curRow) + Screen.TwipsPerPixelY If Y < totHeight Then ' Convert the twips values into pixel coordinates ApiRectFromPoint = curRow r.top = topLocation / Screen.TwipsPerPixelY r.bottom = totHeight / Screen.TwipsPerPixelY r.left = 0 r.right = ctl.Width / Screen.TwipsPerPixelY Exit Function End If Next curRow ApiRectFromPoint = -1 ' indicate failure End Function Sub ApptEdit () ' This subroutine moves the data in the current grid row into ' the "post-it" editing area. Dim aText As String Dim colonPos As Integer ' This routine copies appointment data to the edit window ApptList.Col = 1 aText = ApptList.Text colonPos = InStr(aText, ":") ' If no colon, there's no appointment, so clear the post-it ' area. If there is a colon, fill in the information. If colonPos = 0 Then ApptText.Text = "" ApptTime.Text = Format$(0, ApptTime.Format) ApptType.Text = "" Else ApptType.Text = Left$(aText, colonPos - 1) ApptText.Text = Mid$(aText, colonPos + 2) ApptList.Col = 0 ApptTime.Text = Format$(ApptList.Text, ApptTime.Format) End If End Sub Sub ApptList_DragDrop (Source As Control, X As Single, Y As Single) ' Drop a new appointment or existing appointment at a new ' row position. Dim aText As String Dim i% If Not EndDragMode(MASK_NEWAPPT Or MASK_OLDAPPT) Then Exit Sub UnhighlightRow IgnoreRowChange = True If DragType = MASK_NEWAPPT Then ApptList.Col = 1 ApptList.Row = GridDropRow ApptList.Text = Source.Tag & ": " ApptEdit Else ApptList.Col = 0 ApptList.Row = GridDropRow aText = ApptList.Text ApptList.Row = DragRow i% = ChangeApptTime(TimeValue(aText)) End If IgnoreRowChange = False ApptText.SetFocus End Sub Sub ApptList_DragOver (Source As Control, X As Single, Y As Single, State As Integer) ' When dragging over the grid, both new and old appointments ' are considered. For both cases, we unhighlight the current ' destination row upon leaving the drop zone, and assure that ' the row under the point is highlighted otherwise. If Not DragValid(Source, MASK_NEWAPPT Or MASK_OLDAPPT, State) Then Exit Sub End If Select Case State Case LEAVE UnhighlightRow Case Else GridDropRow = HighlightRowAtPoint(X, Y) End Select End Sub Sub ApptList_MouseDown (Button As Integer, Shift As Integer, X As Single, Y As Single) ' We take charge of the mouse down event to initiate dragging ' ourselves. First, the cursor must be in column 1. Next, ' the row must contain a valid appointment to be grabbed ' (identified by the presence of a colon in the cell). If AtGridCol(ApptList, X, Y) > 0 Then If InStr(ApptList.Text, ":") <> 0 Then ' The timer will now count down. This allows the user ' to easily click, or "press" the mouse. The Timer ' event handles the drag initialization. GridTimer.Enabled = True End If End If End Sub Sub ApptList_MouseUp (Button As Integer, Shift As Integer, X As Single, Y As Single) ' Be sure the timer is disabled so that a click doesn't ' initiate a drag. If it's already disabled, it doesn't matter. GridTimer.Enabled = False End Sub Sub ApptList_RowColChange () ' Whenever the row changes, move the highlight to track the ' current cell. ApptList.SelStartRow = ApptList.Row ApptList.SelEndRow = ApptList.Row ' IgnoreRowChange means that we're setting Col or Row somewhere ' else in the code and we don't want ApptEdit to be called. ' Otherwise, the user changed the row and we update the ' "post-it" area. If Not IgnoreRowChange Then IgnoreRowChange = True ApptEdit IgnoreRowChange = False End If End Sub Sub ApptText_DragDrop (Source As Control, X As Single, Y As Single) valid% = EndDragMode(MASK_NONE) End Sub Sub ApptText_DragOver (Source As Control, X As Single, Y As Single, State As Integer) valid% = DragValid(Source, MASK_NONE, State) End Sub Sub ApptTime_DragDrop (Source As Control, X As Single, Y As Single) valid% = EndDragMode(MASK_NONE) End Sub Sub ApptTime_DragOver (Source As Control, X As Single, Y As Single, State As Integer) valid% = DragValid(Source, MASK_NONE, State) End Sub Sub ApptTime_ValidationError (InvalidText As String, StartPosition As Integer) MsgBox "Invalid time" ApptTime.SetFocus End Sub Sub ApptType_DragDrop (Source As Control, X As Single, Y As Single) ' Accept a drop only for a NEWAPPT icon, otherwise the ' operation will be cancelled. If EndDragMode(MASK_NEWAPPT) Then ApptType.Text = Source.Tag End If End Sub Sub ApptType_DragOver (Source As Control, X As Single, Y As Single, State As Integer) valid% = DragValid(Source, MASK_NEWAPPT, State) End Sub Sub ApptType_KeyPress (KeyAscii As Integer) ' Don't allow a colon to be entered, since we use a colon to ' separate the appointment "kind" from the text. If KeyAscii = Asc(":") Then Beep KeyAscii = 0 End If End Sub Function AtGridCol (ctl As Control, X As Single, Y As Single) ' Given a point on a grid control, in twips, this routine ' returns the column number where the point is located, or ' -1 indicating the point is outside the grid. Dim curCol As Integer Dim totWidth As Single ' Loop through each column, accumulating column width until we ' reach the column containing the point. For curCol = 0 To ctl.Cols - 1 totWidth = totWidth + ctl.ColWidth(curCol) + Screen.TwipsPerPixelX If X < totWidth Then AtGridCol = curCol Exit Function End If Next curCol AtGridCol = -1 ' not found End Function Sub BeginDragMode (ctl As Control, objType As Integer) ' Whenever a drag is about to start, this routine is called. ' The type mask of the drag is flagged, and we remember that ' dragging is in progress. This routine MUST be matched ' by an EndDragMode function call. DragType = objType Dragging = True ' Start the drag process ctl.Drag BEGIN_DRAG End Sub Function ChangeApptTime (newtime As Variant) As Integer ' Given a new time for an appointment at the current row, this ' routine moves the appointment to the new location in the ' grid. Dim trow As Integer Dim oldAppt As String trow = TimeRow(newtime) ' If we're already there, then do nothing and return False, ' indicating no row change occurred. If trow = ApptList.Row Then ChangeApptTime = False Exit Function End If ChangeApptTime = True IgnoreRowChange = True ' Actually move the row. ApptList.Col = 1 oldAppt = ApptList.Text ApptList.Text = "" ApptList.Row = trow ApptList.Text = oldAppt ApptEdit ' move the data to the post-it area IgnoreRowChange = False End Function Function DragValid (src As Control, mask As Integer, State As Integer) As Integer ' This function is called by an object's DragOver event to ' automatically change the drag cursor to the "no drop" ' cursor if necessary. It also returns True if the object ' can legally be dropped according to the input mask. If (mask And DragType) Then DragValid = True Exit Function End If ' This is not a valid drag. Return False, but also change the ' object's drag icon to the NoDrag icon (remembering the old ' value for later restore when we exit this object). DragValid = False Select Case State Case ENTER ' Entering, remember old icon SaveIcon.DragIcon = src.DragIcon src.DragIcon = NoDrag.DragIcon Case LEAVE ' Exiting, restore old icon src.DragIcon = SaveIcon.DragIcon End Select End Function Function EndDragMode (mask As Integer) As Integer ' This function is called when a drag has ended, either ' successfully or unsuccessfully. This routine removes any ' user feedback related to the drag operation and returns ' TRUE if the passed mask matches the dragged object. Select Case DragType Case MASK_NEWAPPT ' If a "new appointment" icon was dragged, change the ' frame background to LTGREY again so that the drag ' is officially over. KindFrame(DragIndex).BackColor = LTGREY Case MASK_OLDAPPT ' If this is an item dragged from the grid, refresh ' the grid in case the drag ended outside the grid ' frame (and the inverted row remains). ApptList.Refresh End Select Dragging = False EndDragMode = (mask And DragType) <> 0 End Function Sub Form_DragDrop (Source As Control, X As Single, Y As Single) ' Ignore drops which occur on the form valid% = EndDragMode(MASK_NONE) End Sub Sub Form_DragOver (Source As Control, X As Single, Y As Single, State As Integer) ' Assure that the "no drop" icon is displayed when passing ' over the form. valid% = DragValid(Source, MASK_NONE, State) End Sub Sub Form_Load () Dim curTime As Variant Dim curRow As Integer Dim rowMax As Integer ' Initialize the grid column widths, and set the height of ' the list so it displays all times entered. rowMax = (Prefs.timeEnd - Prefs.timeStart) / Prefs.timeIncrement ApptList.ColWidth(0) = ApptForm.TextWidth("XX:XX XX") ApptList.ColWidth(1) = ApptList.Width - ApptList.ColWidth(0) ApptList.Height = (ApptList.RowHeight(0) + Screen.TwipsPerPixelY) * rowMax IgnoreRowChange = True ApptList.Rows = rowMax ApptList.Col = 0 ' Fill the leftmost column with appointment times. For curTime = Prefs.timeStart To Prefs.timeEnd Step Prefs.timeIncrement ApptList.Row = curRow ApptList.Text = Format$(curTime, "hh:mm am/pm") curRow = curRow + 1 Next curTime IgnoreRowChange = False ApptList.Row = 0 End Sub Sub Form_MouseMove (Button As Integer, Shift As Integer, X As Single, Y As Single) ' Since we can't trap a "drop" which occurs outside of our ' application, this is a pretty good solution. Whenever the ' cursor passes over the form, if we're still dragging check ' to see if the button is now up. If so, just cancel the ' operation If Dragging Then If (Button And LEFT_BUTTON) = 0 Then valid% = EndDragMode(MASK_NONE) End If End If End Sub Sub GridTimer_Timer () ' When the timer is triggered, the user has been holding the ' mouse down over a grid row for a "press" duration. Now, ' initiate a drag operation. ' Reset the column to the one with the text in it. IgnoreRowChange = True ApptList.Col = 1 IgnoreRowChange = False ' Indicate we're doing an "old appointment" drag. DragRow = ApptList.Row ApptList.DragIcon = MoveIcon.DragIcon BeginDragMode ApptList, MASK_OLDAPPT GridTimer.Enabled = False End Sub Function HighlightRowAtPoint (X As Single, Y As Single) As Integer ' If the ApplList grid was highlighted (according to the ' GridInverted variable), then unhighlight the old location and ' highlight the new one. Instead of a row number, a point within ' the grid is passed. The row number is returned, or -1, meaning ' that the point was outside the grid. Dim newrect As RECT Dim rownum As Integer Dim gridDC As Integer rownum = ApiRectFromPoint(ApptList, X, Y, newrect) HighlightRowAtPoint = rownum ' Don't rehighlight the current row, just exit. If rownum >= 0 And GridInverted And newrect.top = GridInvertRect.top Then Exit Function ' Use the Windows API call InvertRect to invert the row we're ' passing above. gridDC = GetDC(ApptList.hWnd) If GridInverted Then InvertRect gridDC, GridInvertRect GridInverted = True If rownum >= 0 Then GridInvertRect = newrect InvertRect gridDC, GridInvertRect GridInverted = True Else GridInverted = False End If gridDC = ReleaseDC(ApptList.hWnd, gridDC) End Function Sub Image1_DragDrop (Source As Control, X As Single, Y As Single) valid% = EndDragMode(MASK_NONE) End Sub Sub Image1_DragOver (Source As Control, X As Single, Y As Single, State As Integer) valid% = DragValid(Source, MASK_NONE, State) End Sub Sub KindFrame_DragDrop (Index As Integer, Source As Control, X As Single, Y As Single) valid% = EndDragMode(MASK_NONE) End Sub Sub KindFrame_DragOver (Index As Integer, Source As Control, X As Single, Y As Single, State As Integer) valid% = DragValid(Source, MASK_NEWAPPT, State) End Sub Sub KindPict_DragDrop (Index As Integer, Source As Control, X As Single, Y As Single) valid% = EndDragMode(MASK_NONE) End Sub Sub KindPict_DragOver (Index As Integer, Source As Control, X As Single, Y As Single, State As Integer) valid% = DragValid(Source, MASK_NEWAPPT, State) End Sub Sub KindPict_MouseDown (Index As Integer, Button As Integer, Shift As Integer, X As Single, Y As Single) ' When the left button goes down over an "appointment type" ' icon, drag its image in NEWAPPT mode. Copy the DragIcon ' each time, since it may still be set to the "no drop" icon ' from a previous cancellation. If Button And LEFT_BUTTON Then KindFrame(Index).DragIcon = DragArrow.DragIcon BeginDragMode KindFrame(Index), MASK_NEWAPPT KindFrame(Index).BackColor = CYAN ' Save the index, we'll need it in EndDragMode DragIndex = Index End If End Sub Sub Label1_DragDrop (Source As Control, X As Single, Y As Single) valid% = EndDragMode(MASK_NONE) End Sub Sub Label1_DragOver (Source As Control, X As Single, Y As Single, State As Integer) valid% = DragValid(Source, MASK_NONE, State) End Sub Sub Label2_DragDrop (Source As Control, X As Single, Y As Single) valid% = EndDragMode(MASK_NONE) End Sub Sub Label2_DragOver (Source As Control, X As Single, Y As Single, State As Integer) valid% = DragValid(Source, MASK_NONE, State) End Sub Sub Panel3D1_DragDrop (Source As Control, X As Single, Y As Single) valid% = EndDragMode(MASK_NONE) End Sub Sub Panel3D1_DragOver (Source As Control, X As Single, Y As Single, State As Integer) valid% = DragValid(Source, MASK_NONE, State) End Sub Sub SaveButton_Click () ' Save all data in the post-it area to the grid. Dim i% IgnoreRowChange = True ApptList.Col = 1 ' We can only save if there's an appointment on the current ' grid row already (at least a blank one). If InStr(ApptList.Text, ":") = 0 Then MsgBox "No appointment at current row" Exit Sub End If ApptList.Text = ApptType.Text & ": " & ApptText.Text IgnoreRowChange = False ' If the time was changed manually, then move the row to the new ' location. i% = ChangeApptTime(TimeValue(ApptTime.Text)) End Sub Sub SaveButton_DragOver (Source As Control, X As Single, Y As Single, State As Integer) valid% = DragValid(Source, MASK_NONE, State) End Sub Function TimeRow (thetime As Variant) As Integer ' Given a time value, return the row number within the grid ' where the specified time slot is located. TimeRow = (thetime - Prefs.timeStart) / Prefs.timeIncrement End Function Sub TrashCan_DragDrop (Source As Control, X As Single, Y As Single) ' The trash can only accepts drops for "old appointments" from ' the grid. If EndDragMode(MASK_OLDAPPT) Then ' Get rid of feedback TrashCan.Picture = TrashClosed.Picture ' Clear the grid row and update the post-it area IgnoreRowChange = True ApptList.Row = DragRow ApptList.Col = 1 ApptList.Text = "" ApptEdit ApptList.SetFocus IgnoreRowChange = False End If End Sub Sub TrashCan_DragOver (Source As Control, X As Single, Y As Single, State As Integer) ' Provide feedback by "opening the trashcan" whenever an ' old appointment is dragged over the trash. If DragValid(Source, MASK_OLDAPPT, State) Then Select Case State Case ENTER ' Open when entering TrashCan.Picture = TrashOpened.Picture Case LEAVE ' Close when leaving TrashCan.Picture = TrashClosed.Picture End Select End If End Sub Sub UnhighlightRow () ' If the ApptList grid is highlighted (according to the ' GridInverted flag), then unhighlight it, otherwise do ' nothing. Dim gridDC As Integer If Not GridInverted Then Exit Sub ' Use the invert rectangle saved by HighlightRowAtPoint gridDC = GetDC(ApptList.hWnd) InvertRect gridDC, GridInvertRect gridDC = ReleaseDC(ApptList.hWnd, gridDC) GridInverted = False End Sub